Skip to content

feat: add MCP Apps (SEP-1865) support#1335

Draft
mattdholloway wants to merge 11 commits into
mainfrom
feat/mcp-apps-support
Draft

feat: add MCP Apps (SEP-1865) support#1335
mattdholloway wants to merge 11 commits into
mainfrom
feat/mcp-apps-support

Conversation

@mattdholloway
Copy link
Copy Markdown

@mattdholloway mattdholloway commented May 19, 2026

Adds opt-in MCP Apps (SEP-1865) support to the SDK.

What

  • New enableMcpApps session capability (SessionConfig + ResumeSessionConfig). When true, the runtime advertises the extensions.io.modelcontextprotocol/ui extension to MCP servers and exposes the session.rpc.mcp.apps.{listTools,callTool,readResource,setHostContext,getHostContext} JSON-RPC methods. Defaults to false so hosts without an iframe renderer don't accidentally register UI-enabled tool variants they can't display.
  • Two new pure helpers in the Node SDK for hosts that render ui:// MCP App bundles in iframes:
    • buildMcpAppsCspHeader(csp) — builds the Content-Security-Policy header per SEP-1865 §UI Resource Format + §Security Implications. Emits the restrictive default (connect-src 'none') when _meta.ui.csp is absent, and the constructed default (connect-src 'self' + declared domains, etc.) when it is declared (even with empty arrays).
    • buildMcpAppsAllowAttribute(permissions) — maps _meta.ui.permissions to the iframe allow attribute (Permission Policy), including the hyphenated clipboard-write name.
  • Regenerated RPC/session-event types across Node, Python, Go, .NET, and Rust to pick up the new schema.
 sequenceDiagram
     autonumber
     participant Host as Host App
     participant SDK as Copilot SDK
     participant Sandbox as mcpAppsSandbox helpers
     participant Conn as JSON-RPC Connection
     participant Runtime as Copilot Runtime
     participant Iframe as ui iframe
 
     Note over Host,Runtime: 1. Opt-in via SDK config
     Host->>SDK: createSession({ enableMcpApps: true })
     SDK->>Conn: session.create { ..., requestMcpApps: true }
     Conn->>Runtime: JSON-RPC request
     Runtime-->>Conn: CreateSessionResponse
     Conn-->>SDK: response
     SDK-->>Host: CopilotSession
 
     Note over Runtime,Host: 2. Tool execution events forwarded unchanged
     Runtime-->>Conn: tool.execution_complete { uiResource, toolDescription._meta.ui }
     Conn-->>SDK: SessionEvent (untyped pass-through)
     SDK-->>Host: onEvent(event)
 
     Note over Host,Iframe: 3. Host builds sandboxed iframe using SDK helpers
     Host->>Sandbox: buildMcpAppsCspHeader(uiResource._meta.ui.csp)
     Sandbox-->>Host: CSP header value
     Host->>Sandbox: buildMcpAppsAllowAttribute(uiResource._meta.ui.permissions)
     Sandbox-->>Host: allow attribute value
     Host->>Iframe: render with CSP + allow + uiResource
 
     Note over Iframe,Runtime: 4. App-to-server RPCs (no typed SDK surface)
     Iframe->>Host: postMessage RPC
     Host->>Conn: raw JSON-RPC: session.rpc.mcp.apps.{listTools|callTool|readResource}
     Conn->>Runtime: forwarded
     Runtime-->>Conn: result (+ optional ephemeral mcp_app.tool_call_complete event)
     Conn-->>SDK: SessionEvent (untyped pass-through, callTool only)
     SDK-->>Host: onEvent(event)
     Conn-->>Host: RPC response
     Host-->>Iframe: postMessage response
 
     Note over Host,Runtime: 5. Resume preserves opt-in
     Host->>SDK: resumeSession({ enableMcpApps: true })
     SDK->>Conn: session.resume { ..., requestMcpApps: true }
Loading

Closes https://github.com/github/copilot-mcp-core/issues/1715
Depends on https://github.com/github/copilot-agent-runtime/pull/7605

@mattdholloway
Copy link
Copy Markdown
Author

@copilot resolve the merge conflicts in this pull request

Adds opt-in 'enableMcpApps' session capability that advertises the
'extensions.io.modelcontextprotocol/ui' extension to MCP servers and
exposes 'session.rpc.mcp.apps.*' JSON-RPC methods.

Node SDK gains two pure helpers for hosts rendering 'ui://' MCP App
bundles in iframes:

- buildMcpAppsCspHeader — constructs the Content-Security-Policy header
  per SEP-1865 §UI Resource Format + §Security Implications, including
  the restrictive default ('connect-src none') when '_meta.ui.csp' is
  absent and constructed defaults ('connect-src self', etc.) when it is
  declared.
- buildMcpAppsAllowAttribute — maps '_meta.ui.permissions' to the iframe
  'allow' attribute (Permission Policy).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mattdholloway mattdholloway force-pushed the feat/mcp-apps-support branch from 5f12d41 to 0827b5a Compare May 19, 2026 16:51
@github-actions

This comment has been minimized.

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generated by SDK Consistency Review Agent for issue #1335 · ● 793.2K

Comment thread nodejs/src/types.ts
*
* @default false
*/
enableMcpApps?: boolean;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-SDK consistency gap: enableMcpApps is added here and to ResumeSessionConfig in Node.js, but the equivalent option is missing from the other four SDK implementations.

The requestMcpApps wire flag won't be sent by Python, Go, .NET, or Rust sessions, so hosts using those SDKs can't opt in to MCP Apps support. Suggested additions to bring the other SDKs into parity:

SDK Location What to add
Python python/copilot/client.pycreate_session() + resume_session() signatures enable_mcp_apps: bool = False parameter; payload["requestMcpApps"] = enable_mcp_apps
Go go/types.goSessionConfig + ResumeSessionConfig EnableMcpApps bool field; set req.RequestMcpApps = Bool(true) in client.go when the flag is set
.NET dotnet/src/Types.csSessionConfig + ResumeSessionConfig public bool EnableMcpApps { get; set; } property; pass RequestMcpApps: config.EnableMcpApps ? true : null in Client.cs
Rust rust/src/types.rsSessionConfig + ResumeSessionConfig pub request_mcp_apps: Option<bool> field + pub fn with_request_mcp_apps(mut self, enable: bool) -> Self builder

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot Complete the implementation of the SDK surfaces across all implementations

mattdholloway and others added 3 commits May 20, 2026 11:05
Mirror nodejs enableMcpApps across the other four SDKs so hosts using
them can opt into MCP Apps (SEP-1865) UI passthrough by sending
requestMcpApps on session.create / session.resume.

- python: enable_mcp_apps kwarg on create_session / resume_session
- go: EnableMcpApps field on SessionConfig / ResumeSessionConfig
- dotnet: EnableMcpApps property on SessionConfig / ResumeSessionConfig
- rust: request_mcp_apps field + with_request_mcp_apps builder

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

mattdholloway and others added 3 commits May 20, 2026 14:55
Port the CSP directive injection defense from copilot-agent-runtime PR
#7605 into the SDK. Without sanitization, an MCP server returning
`frameDomains: ['evil.com; form-action *']` could break out of one CSP
directive and inject sibling directives (CSP first-occurrence rule then
lets an earlier injected `script-src *` win).

Each server-supplied entry is now:
- rejected if it contains CSP metacharacters ([;,\\s'"\\\\])
- accepted verbatim for the bare-scheme allowlist (data:, blob:,
  mediastream:, filesystem:)
- otherwise parsed via URL and canonicalized to its origin; opaque
  origins (where `URL.origin` is the literal string 'null') are dropped

Adds 10 sanitization tests mirroring runtime PR coverage.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Reflect the runtime-side gate added in copilot-agent-runtime PR #7605:
requestMcpApps is now honored server-side only when the MCP_APPS feature
flag or COPILOT_MCP_APPS=true env override is set; otherwise the opt-in
is silently dropped (the runtime logs a warning, but the SDK consumer
sees nothing). Update the JSDoc / docstrings on Node, Go, .NET, and Rust
to document this and to point at capabilities.ui.mcpApps on the
create/resume response as the way to detect the silent drop. Also adds
the diagnose method to the enumerated mcp.apps.* RPCs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

mattdholloway and others added 2 commits May 20, 2026 16:31
Expose the runtime's response capability so consumers can detect when
their enableMcpApps opt-in was silently dropped by the runtime gate
(MCP_APPS feature flag / COPILOT_MCP_APPS env override unset).

For each SDK:
- Add mcpApps?: bool to the SessionUiCapabilities type
- After session.create / session.resume, if the consumer requested the
  opt-in but capabilities.ui.mcpApps is not true on the response, log
  a warning (console.warn / logger.warning / slog / tracing::warn /
  fmt.Fprintf(os.Stderr, ...)) so the silent drop is discoverable.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread nodejs/src/client.ts
): void {
if (requested && !capabilities?.ui?.mcpApps) {
console.warn(
`[copilot-sdk] Session ${sessionId}: enableMcpApps was requested but the runtime did not advertise capabilities.ui.mcpApps. The runtime's MCP_APPS feature flag or COPILOT_MCP_APPS=true environment override is likely unset; the MCP Apps surface is unavailable for this session.`
@github-actions

This comment has been minimized.

- python: ruff format reflowed the new _warn_if_mcp_apps_dropped helper
- rust: tests/e2e/elicitation.rs constructs UiCapabilities as a struct
  literal; the new mcp_apps field made it non-exhaustive

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

Cross-SDK Consistency Review ✅

This PR adds MCP Apps (SEP-1865) opt-in support across all five SDK implementations. Here's my consistency assessment:

What's consistent across all SDKs

SDK Config field Warning function Create + Resume Wire field
Node.js enableMcpApps?: boolean warnIfMcpAppsDropped requestMcpApps
Python enable_mcp_apps: bool = False _warn_if_mcp_apps_dropped requestMcpApps
Go EnableMcpApps bool warnIfMcpAppsDropped requestMcpApps
.NET EnableMcpApps { get; set; } WarnIfMcpAppsDropped requestMcpApps
Rust request_mcp_apps: Option<bool> warn_if_mcp_apps_dropped requestMcpApps

All five SDKs:

  • Expose the opt-in on both SessionConfig and ResumeSessionConfig
  • Pass the flag through to the session.create / session.resume wire request
  • Emit a warning when the runtime silently drops the opt-in (capabilities.ui.mcpApps absent in response)
  • Follow each language's established naming conventions (Rust uses request_* consistently for all capability flags, matching its existing request_elicitation, request_exit_plan_mode, etc.)

Node.js-only helpers (buildMcpAppsCspHeader, buildMcpAppsAllowAttribute)

These are pure CSP header / Permission Policy allow attribute builders for hosts that render ui:// MCP App bundles in iframes. They're scoped to Node.js because that's the primary ecosystem for web host apps embedding iframes. This is a deliberate design choice, not a parity gap — the functions themselves are straightforward enough that any other SDK user who needs them in Python/Go/.NET can reimplement them trivially from the spec.

If MCP App iframe rendering becomes common in Python or .NET host contexts in the future, it would be worth adding equivalent helpers there too, but that's a separate decision.

No blocking consistency issues found

The PR maintains feature parity across all language SDKs and follows each SDK's established conventions. 👍

Generated by SDK Consistency Review Agent for issue #1335 · ● 1.1M ·

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants